package com.lkd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.lkd.common.VMSystem;

import com.lkd.config.ConsulConfig;
import com.lkd.config.TopicConfig;
import com.lkd.contract.*;

import com.lkd.dao.VendingMachineDao;

import com.lkd.emq.MqttProducer;
import com.lkd.entity.*;
import com.lkd.exception.LogicException;
import com.lkd.http.vo.CreateVMReq;
import com.lkd.service.*;

import com.lkd.utils.DistributedLock;
import com.lkd.utils.UUIDUtils;
import com.lkd.vo.Pager;
import com.lkd.vo.SkuVO;
import com.lkd.vo.VmVO;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class VendingMachineServiceImpl extends ServiceImpl<VendingMachineDao, VendingMachineEntity> implements VendingMachineService {

    @Autowired
    private NodeService nodeService;

    @Autowired
    private ChannelService channelService;

    @Autowired
    private VmTypeService vmTypeService;

    @Autowired
    private MqttProducer mqttProducer;

    @Autowired
    private VendoutRunningService vendoutRunningService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ConsulConfig consulConfig;

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean add(CreateVMReq vendingMachine) {
        VendingMachineEntity vendingMachineEntity = new VendingMachineEntity();
        vendingMachineEntity.setNodeId(Long.valueOf(vendingMachine.getNodeId()));
        vendingMachineEntity.setVmType(vendingMachine.getVmType());
        NodeEntity nodeEntity = nodeService.getById(vendingMachine.getNodeId());
        if (nodeEntity == null) {
            throw new LogicException("所选点位不存在");
        }
        //复制属性
        BeanUtils.copyProperties(nodeEntity, vendingMachineEntity);
        vendingMachineEntity.setCreateUserId(Long.valueOf(vendingMachine.getCreateUserId()));
        vendingMachineEntity.setInnerCode(UUIDUtils.getUUID());
        vendingMachineEntity.setClientId(UUIDUtils.generateClientId(vendingMachineEntity.getInnerCode()));
        this.save(vendingMachineEntity);
        //创建货道数据
        createChannel(vendingMachineEntity);
        return true;
    }


    /**
     * 创建货道
     *
     * @param vm
     * @return
     */
    private boolean createChannel(VendingMachineEntity vm) {
        VmTypeEntity vmType = vmTypeService.getById(vm.getVmType());
        List<ChannelEntity> channelList = Lists.newArrayList();
        for (int i = 1; i <= vmType.getVmRow(); i++) {
            for (int j = 1; j <= vmType.getVmCol(); j++) {
                ChannelEntity channel = new ChannelEntity();
                channel.setChannelCode(i + "-" + j);
                channel.setCurrentCapacity(0);
                channel.setInnerCode(vm.getInnerCode());
                channel.setLastSupplyTime(vm.getLastSupplyTime());
                channel.setMaxCapacity(vmType.getChannelMaxCapacity());
                channel.setVmId(vm.getId());
                channelList.add(channel);
            }
        }
        channelService.saveBatch(channelList);
        return true;
    }


    @Override
    public boolean update(Long id, Long nodeId) {
        VendingMachineEntity vm = this.getById(id);
        if (vm.getVmStatus() == VMSystem.VM_STATUS_RUNNING)
            throw new LogicException("改设备正在运营");
        NodeEntity nodeEntity = nodeService.getById(nodeId);
        BeanUtils.copyProperties(nodeEntity, vm);
        return this.updateById(vm);
    }


    @Override
    public Pager<String> getAllInnerCodes(boolean isRunning, long pageIndex, long pageSize) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<VendingMachineEntity> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageIndex, pageSize);

        QueryWrapper<VendingMachineEntity> qw = new QueryWrapper<>();
        if (isRunning) {
            qw.lambda()
                    .select(VendingMachineEntity::getInnerCode)
                    .eq(VendingMachineEntity::getVmStatus, 1);
        } else {
            qw.lambda()
                    .select(VendingMachineEntity::getInnerCode)
                    .ne(VendingMachineEntity::getVmStatus, 1);
        }
        this.page(page, qw);
        Pager<String> result = new Pager<>();
        result.setCurrentPageRecords(page.getRecords().stream().map(VendingMachineEntity::getInnerCode).collect(Collectors.toList()));
        result.setPageIndex(page.getCurrent());
        result.setPageSize(page.getSize());
        result.setTotalCount(page.getTotal());

        return result;
    }

    @Override
    public Pager<VendingMachineEntity> query(Long pageIndex, Long pageSize, Integer status, String innerCode) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<VendingMachineEntity> page
                = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<VendingMachineEntity> queryWrapper = new LambdaQueryWrapper<>();
        if (status != null) {
            queryWrapper.eq(VendingMachineEntity::getVmStatus, status);
        }
        if (!Strings.isNullOrEmpty(innerCode)) {
            queryWrapper.likeLeft(VendingMachineEntity::getInnerCode, innerCode);
        }
        this.page(page, queryWrapper);

        return Pager.build(page);
    }


    @Override
    public VmVO findByInnerCode(String innerCode) {
        LambdaQueryWrapper<VendingMachineEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VendingMachineEntity::getInnerCode, innerCode);
        VendingMachineEntity vm = this.getOne(queryWrapper);
        VmVO vmVO = new VmVO();
        BeanUtils.copyProperties(vm, vmVO);
        vmVO.setNodeAddr(vm.getNode().getAddr());//地址
        vmVO.setNodeName(vm.getNode().getName());//名称
        return vmVO;
    }


    @Override
    public boolean updateStatus(String innerCode, Integer status) {
        try {
            UpdateWrapper<VendingMachineEntity> uw = new UpdateWrapper<>();
            uw.lambda()
                    .eq(VendingMachineEntity::getInnerCode, innerCode)
                    .set(VendingMachineEntity::getVmStatus, status);
            this.update(uw);

        } catch (Exception ex) {
            log.error("updateStatus error,innerCode is " + innerCode + " status is " + status, ex);
            return false;
        }
        return true;
    }


    @Override
    public List<SkuVO> getSkuListByInnerCode(String innerCode) {
        //获取货道列表
        List<ChannelEntity> channelList = channelService.getChannelesByInnerCode(innerCode).stream()
                .filter(c -> c.getSkuId() > 0 && c.getSku() != null).collect(Collectors.toList());
        //获取有商品的库存余量
        Map<SkuEntity, Integer> skuMap = channelList.stream()
                .collect(Collectors.groupingBy(
                        ChannelEntity::getSku,
                        Collectors.summingInt(ChannelEntity::getCurrentCapacity)));//对库存数求和
        return skuMap.entrySet().stream().map(entry -> {
            SkuEntity sku = entry.getKey(); //查询商品
            SkuVO skuVO = new SkuVO();
            BeanUtils.copyProperties(sku, skuVO);
            skuVO.setImage(sku.getSkuImage());//图片
            skuVO.setCapacity(entry.getValue());
            skuVO.setRealPrice(sku.getPrice());//真实价格
            return skuVO;
        }).sorted(Comparator.comparing(SkuVO::getCapacity).reversed())  //按库存量降序排序
                .collect(Collectors.toList());
    }

    @Override
    public Boolean hasCapacity(String innerCode, Long skuId) {
        var qw = new LambdaQueryWrapper<ChannelEntity>();
        qw
                .eq(ChannelEntity::getInnerCode, innerCode)
                .eq(ChannelEntity::getSkuId, skuId)
                .gt(ChannelEntity::getCurrentCapacity, 0);
        return channelService.count(qw) > 0;
    }

    /**
     * 补货，需要修改商品库存
     *
     * @param supplyContract
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean supply(SupplyContract supplyContract) {
        //1.修改售货机表商品上次补货时间
        LambdaUpdateWrapper<VendingMachineEntity> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(VendingMachineEntity::getInnerCode, supplyContract.getInnerCode())
                .set(VendingMachineEntity::getLastSupplyTime, LocalDateTime.now());
        update(wrapper);

        //2.更改库存
        //2.1 根据售货机编号查询出对应的货道数据
        List<ChannelEntity> channelEntities = channelService.getChannelesByInnerCode(supplyContract.getInnerCode());

        supplyContract.getSupplyData().forEach(c -> {
            //筛选货道
            Optional<ChannelEntity> item = channelEntities.stream().filter(channel ->
                    channel.getChannelCode().equals(c.getChannelId())).findFirst();
            //2.2更新货道库存
            if (item.isPresent()) {
                var channelEntity = item.get();
                channelEntity.setCurrentCapacity(channelEntity.getCurrentCapacity() + c.getCapacity());
                //更新货道的补货时间
                channelEntity.setLastSupplyTime(LocalDateTime.now());
                channelService.updateById(channelEntity);
            }
        });

        return true;
    }


    /**
     * 发送补货协议到emq
     *
     * @param innerCode
     */
    @Override
    public void sendSupplyTask(String innerCode) {
        //1.根据售货机编号查询货道信息，需要排除skuId = 0的货道（表示该货道没有放商品）
        //构建查询信息
        LambdaQueryWrapper<ChannelEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ChannelEntity::getInnerCode, innerCode)
                .ne(ChannelEntity::getSkuId, 0L);
        List<ChannelEntity> channelEntityList = channelService.list(wrapper);

        //2.构建补货列表
        List<SupplyChannel> supplyChannels = channelEntityList.stream()
                .filter(c -> c.getCurrentCapacity() < c.getMaxCapacity()) //当前容量小于最大容量需要补货
                .map(c -> {
                    SupplyChannel supplyChannel = new SupplyChannel();
                    //货道编号
                    supplyChannel.setChannelId(c.getChannelCode());
                    //补货容量
                    supplyChannel.setCapacity(c.getMaxCapacity() - c.getCurrentCapacity());
                    //商品编号、名称、图片
                    supplyChannel.setSkuId(c.getSkuId());
                    supplyChannel.setSkuName(c.getSku().getSkuName());
                    supplyChannel.setSkuImage(c.getSku().getSkuImage());
                    return supplyChannel;
                }).collect(Collectors.toList());

        //3.发送消息
        //封装协议
        if (supplyChannels.size() > 0) {
            SupplyContract supplyContract = new SupplyContract();
            supplyContract.setInnerCode(innerCode);
            supplyContract.setSupplyData(supplyChannels);
            //发送消息
            try {
                mqttProducer.send(TopicConfig.TASK_SUPPLY_TOPIC, 2, supplyContract);
            } catch (JsonProcessingException e) {
                log.error("发送补货工单协议失败");
                e.printStackTrace();
            }
        }
    }


    /**
     * 处理出货逻辑
     *
     * @param vendoutContract
     * @return
     */
    @Override
    @Transactional //涉及多张表，需要开启事务
    public boolean vendOut(VendoutContract vendoutContract) {
        try {
            //1.根据售货机编号和商品id查询有商品库存的货道
            //1.1 获取售货机所有货道
            List<ChannelEntity> channels = channelService.getChannelesByInnerCode(vendoutContract.getInnerCode());
            //1.2 查询商品的货道（有库存）
            Optional<ChannelEntity> optional = channels.stream()
                    .filter(c -> c.getSkuId().equals(vendoutContract.getVendoutData().getSkuId()))
                    .filter(c -> c.getCurrentCapacity() > 0).findFirst(); //判断是否有库存

            //2.扣减库存
            if (optional.isPresent()) { //如果存在货道有库存
                var channelEntity = optional.get();
                //扣减库存
                channelEntity.setCurrentCapacity(channelEntity.getCurrentCapacity() - 1);
                //更新数据库中数据
                channelService.updateById(channelEntity);

                //3.记录出货流水，状态默认为false
                var vendoutRunningEntity = new VendoutRunningEntity();
                vendoutRunningEntity.setOrderNo(vendoutContract.getVendoutData().getOrderNo());
                vendoutRunningEntity.setInnerCode(vendoutContract.getInnerCode());
                vendoutRunningEntity.setStatus(false);
                vendoutRunningEntity.setSkuId(vendoutContract.getVendoutData().getSkuId());

                vendoutRunningService.save(vendoutRunningEntity);

                //4.发送出货消息给售货机终端
                //协议中加上货道编号
                var vendoutData = vendoutContract.getVendoutData();
                vendoutData.setChannelCode(channelEntity.getChannelCode());
                vendoutContract.setVendoutData(vendoutData);

                mqttProducer.send(TopicConfig.getVendoutTopic(vendoutContract.getInnerCode()), 2, vendoutContract);

                //解锁售货机状态
                String sessionId = redisTemplate.boundValueOps(VMSystem.VM_LOCK_KEY_PREF
                        + vendoutContract.getInnerCode()).get();
                System.err.println(sessionId + "====================");

                DistributedLock lock = new DistributedLock(
                        consulConfig.getConsulRegisterHost(),
                        consulConfig.getConsulRegisterPort()
                );
                lock.releaseLock(sessionId);

                return true;
            } else {
                //商品无库存
                log.info("当前商品已售空");
                return false;
            }
        } catch (Exception e) {
            log.error("售货机微服务处理出货逻辑失败", e);
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 处理出货结果
     *
     * @param vendoutResultContract
     * @return
     */
    @Override
    public boolean vendOutResult(VendoutResultContract vendoutResultContract) {
        //1.如果出货失败，记录日志并触发退款流程
        if (!vendoutResultContract.isSuccess()) {
            log.info("售货机编号：{}出货失败，进入退款流程", vendoutResultContract.getInnerCode());

            //回滚库存
            ChannelEntity channelInfo = channelService.getChannelInfo(vendoutResultContract.getInnerCode()
                    , vendoutResultContract.getVendoutData().getChannelCode());

            channelInfo.setCurrentCapacity(channelInfo.getCurrentCapacity() + 1);
            channelService.updateById(channelInfo);

            return false;
        }
        //2.出货成功，将出货记录里的状态修改为true
        LambdaQueryWrapper<VendoutRunningEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(VendoutRunningEntity::getOrderNo, vendoutResultContract.getVendoutData().getOrderNo());

        var vendoutRunningEntity = vendoutRunningService.getOne(wrapper);
        if (vendoutRunningEntity == null) {
            return false;
        }

        vendoutRunningEntity.setStatus(true);
        vendoutRunningService.updateById(vendoutRunningEntity);

        return true;
    }
}
